﻿using Microsoft.Extensions.Hosting;
using System;
using Orleans;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Orleans.Configuration;
using Orleans.Hosting;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Linq;
using Microsoft.Extensions.Logging;

namespace IOP.Orleans.Client
{
    /// <summary>
    /// 奥尔良客户端扩展
    /// </summary>
    public static class OrleansClientExtensions
    {
        /// <summary>
        /// 创建奥尔良客户端服务
        /// </summary>
        /// <param name="hostBuilder"></param>
        /// <param name="optionName"></param>
        /// <param name="clientAction"></param>
        /// <returns></returns>
        public static IHostBuilder CreateOrleansServicesClient(this IHostBuilder hostBuilder, string optionName, Action<Microsoft.Extensions.Hosting.HostBuilderContext, IOrleansServiceBuilder> servicesAction)
        {
            hostBuilder.ConfigureLogging(logger => logger.AddConsole());
            hostBuilder.ConfigureServices((context, services) =>
            {
                services.AddOptions();
                services.Configure<ClientHostOptions>(context.Configuration.GetSection(optionName));
                var provider = services.BuildServiceProvider();
                IOptions<ClientHostOptions> o = provider.GetRequiredService<IOptions<ClientHostOptions>>();
                ClientHostOptions option = o.Value;
                var clientBuilder = new ClientBuilder();
                clientBuilder.ConfigureClient(context, option);
                services.AddHostedService<OrleansClientMonitorHostService>();

                IOrleansServiceBuilder serviceBuilder = new OrleansServiceBuilder(services, clientBuilder, context);
                servicesAction?.Invoke(context, serviceBuilder);

                IClusterClient client = clientBuilder.Build();
                services.AddSingleton(client);
            });
            return hostBuilder;
        }

        /// <summary>
        /// 创建奥尔良客户端服务
        /// </summary>
        /// <param name="hostBuilder"></param>
        /// <param name="options"></param>
        /// <param name="siloAction"></param>
        /// <returns></returns>
        public static IHostBuilder CreateOrleansServicesClient(this IHostBuilder hostBuilder, Action<ClientHostOptions> options, Action<Microsoft.Extensions.Hosting.HostBuilderContext, IOrleansServiceBuilder> servicesAction)
        {
            hostBuilder.ConfigureLogging(logger => logger.AddConsole());
            hostBuilder.ConfigureServices((context, services) =>
            {
                services.AddOptions();
                if (options == null) throw new ArgumentNullException(nameof(options), "the ClientHostOptions Action is null");
                ClientHostOptions option = new ClientHostOptions();
                options.Invoke(option);
                services.Configure(options);
                var clientBuilder = new ClientBuilder();
                clientBuilder.ConfigureClient(context, option);
                services.AddHostedService<OrleansClientMonitorHostService>();

                IOrleansServiceBuilder serviceBuilder = new OrleansServiceBuilder(services, clientBuilder, context);
                servicesAction?.Invoke(context,serviceBuilder);

                IClusterClient client = clientBuilder.Build();
                services.AddSingleton(client);
            });
            return hostBuilder;
        }

        /// <summary>
        /// 配置服务
        /// </summary>
        /// <param name="serviceBuilder"></param>
        /// <param name="services"></param>
        /// <returns></returns>
        public static IOrleansServiceBuilder ConfigureServices(this IOrleansServiceBuilder serviceBuilder, Action<Microsoft.Extensions.Hosting.HostBuilderContext, IServiceCollection> services)
        {
            services?.Invoke(serviceBuilder.HostBuilderContext, serviceBuilder.Services);
            return serviceBuilder;
        }
        /// <summary>
        /// 配置奥尔良客户端
        /// </summary>
        /// <param name="serviceBuilder"></param>
        /// <param name="buildAction"></param>
        /// <returns></returns>
        public static IOrleansServiceBuilder ConfigureOrleansClient(this IOrleansServiceBuilder serviceBuilder, Action<Microsoft.Extensions.Hosting.HostBuilderContext, IClientBuilder> buildAction)
        {
            buildAction?.Invoke(serviceBuilder.HostBuilderContext, serviceBuilder.ClientBuilder);
            return serviceBuilder;
        }

        /// <summary>
        /// 配置奥尔良客户端
        /// </summary>
        /// <param name="builder"></param>
        /// <param name="options"></param>
        private static void ConfigureClient(this IClientBuilder builder, Microsoft.Extensions.Hosting.HostBuilderContext hostBuilderContext, ClientHostOptions options)
        {
            if (options == null) throw new ArgumentNullException(nameof(ClientHostOptions), "the ClientHostOptions is null");
            if (options.Cluster == null) throw new ArgumentNullException(nameof(options.Cluster), $"The options {nameof(ClusterOptions)} is necessary in orlean server, please add options {nameof(ClusterOptions)} in node configure");
            builder.Configure<ClusterOptions>(o =>
            {
                o.ClusterId = options.Cluster.ClusterId;
                o.ServiceId = options.Cluster.ServiceId;
            });
            if (string.IsNullOrEmpty(options.ClusterProvider)) options.ClusterProvider = ClusteringScheme.Custom;
            switch (options.ClusterProvider)
            {
                case ClusteringScheme.AdoNet:
                    if (options.AdoNetClustering == null) throw new ArgumentNullException(nameof(options.AdoNetClustering), $"Cannot found AdoNetClustering option in ServerHostOptions");
                    builder.UseAdoNetClustering(o =>
                    {
                        o.ConnectionString = options.AdoNetClustering.ConnectionString;
                        o.Invariant = options.AdoNetClustering.Invariant;
                    });
                    break;
                case ClusteringScheme.Local:
                    builder.UseLocalhostClustering();
                    break;
                case ClusteringScheme.Custom:
                    break;
                default:
                    throw new Exception($"Unknow clusterProvider with {options.ClusterProvider}");
            }
        }

        /// <summary>
        /// 加载奥尔良服务
        /// </summary>
        /// <param name="services"></param>
        private static void AddOrleansServices(this IServiceCollection services)
        {
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach(var assembly in assemblies)
            {
                LoadOrleansServicesWithAssembly(assembly, services);
            }
        }
        /// <summary>
        /// 加载奥尔良服务
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="services"></param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void LoadOrleansServicesWithAssembly(Assembly assembly, IServiceCollection services)
        {
            var types = assembly.GetTypes();
            foreach(var type in types)
            {
                var interfaces = type.GetInterfaces();
                if (!interfaces.Where(x => x == typeof(IOrleansService)).Any()) continue;
                foreach(var i in interfaces)
                {
                    if(i.GetInterfaces().Any(x=>x == typeof(IOrleansService)))
                    {
                        services.AddSingleton(i, type);
                        break;
                    }
                }
            }
        }
    }
}
